package diplomovka.subgraphSearcher.componentsFinder;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import diplomovka.graphCreator.modelParts.MAnnotation;
import diplomovka.graphCreator.modelParts.MClass;
import diplomovka.graphCreator.modelParts.MEnumeration;
import diplomovka.graphCreator.modelParts.MException;
import diplomovka.graphCreator.modelParts.MField;
import diplomovka.graphCreator.modelParts.MInterface;
import diplomovka.graphCreator.modelParts.MMethod;
import diplomovka.graphCreator.modelParts.Type;
import diplomovka.subgraphSearcher.generated.Annotation;
import diplomovka.subgraphSearcher.generated.Annotations;
import diplomovka.subgraphSearcher.generated.Clazz;
import diplomovka.subgraphSearcher.generated.ClazzConditions;
import diplomovka.subgraphSearcher.generated.ClazzExports;
import diplomovka.subgraphSearcher.generated.Entities;
import diplomovka.subgraphSearcher.generated.Exception;
import diplomovka.subgraphSearcher.generated.Exceptions;
import diplomovka.subgraphSearcher.generated.Field;
import diplomovka.subgraphSearcher.generated.Fields;
import diplomovka.subgraphSearcher.generated.Interface;
import diplomovka.subgraphSearcher.generated.Interfaces;
import diplomovka.subgraphSearcher.generated.Method;
import diplomovka.subgraphSearcher.generated.Methods;
import diplomovka.subgraphSearcher.generated.Modifiers;
import diplomovka.subgraphSearcher.generated.Parameter;
import diplomovka.subgraphSearcher.generated.Parameters;
import diplomovka.subgraphSearcher.generated.Services;
import diplomovka.subgraphSearcher.generated.SubgraphStatement;
import diplomovka.subgraphSearcher.generated.ValueObjects;
import diplomovka.subgraphSearcher.generated.WebServices;

public class CartridgeFinderImpl implements CartridgeFinder {

	public static final String ENTITIES = "ENTITIES";
	public static final String SERVICES = "SERVICES";
	public static final String VALUE_OBJECTS = "VALUE_OBJECTS";
	public static final String WEB_SERVICES = "WEB_SERVICES";

	private List<Object> graph;

	public CartridgeFinderImpl(List<Object> graph) {
		super();
		this.graph = graph;
	}

	@Override
	public Map<String, List<Object>> findComponentsInGraph(
			SubgraphStatement subgraphStatement) {
		Map<String, List<Object>> resultMap = new HashMap<String, List<Object>>();

		List<Clazz> clazzList;
		// processing of entities
		List<Object> entitiesList = new ArrayList<Object>();
		resultMap.put(ENTITIES, entitiesList);
		Entities entities = subgraphStatement.getEntities();
		if (entities != null) {
			clazzList = entities.getClazz();
			for (Clazz clazz : clazzList) {
				ClazzConditions clazzConditions = clazz.getConditions();
				ClazzExports clazzExports = clazz.getExports();
				entitiesList.addAll(modifyEntitiesForExport(
						filterClasses(clazzConditions), clazzExports));
			}
		}

		// processing of services
		List<Object> servicesList = new ArrayList<Object>();
		resultMap.put(SERVICES, servicesList);
		Services services = subgraphStatement.getServices();
		if (services != null) {
			clazzList = services.getClazz();
			for (Clazz clazz : clazzList) {
				ClazzConditions clazzConditions = clazz.getConditions();
				ClazzExports clazzExports = clazz.getExports();
				servicesList.addAll(modifyServicesForExport(
						filterClasses(clazzConditions), clazzExports));
			}
		}

		// processing of value objects
		List<Object> valueObjectsList = new ArrayList<Object>();
		resultMap.put(VALUE_OBJECTS, valueObjectsList);
		ValueObjects valueObjects = subgraphStatement.getValueObjects();
		if (valueObjects != null) {
			clazzList = valueObjects.getClazz();
			for (Clazz clazz : clazzList) {
				ClazzConditions clazzConditions = clazz.getConditions();
				ClazzExports clazzExports = clazz.getExports();
				valueObjectsList.addAll(modifyValueObjectsForExport(
						filterClasses(clazzConditions), clazzExports));
			}
		}

		// processing of webServices
		List<Object> webServicesList = new ArrayList<Object>();
		resultMap.put(WEB_SERVICES, webServicesList);
		WebServices webServices = subgraphStatement.getWebServices();
		if (webServices != null) {
			clazzList = webServices.getClazz();
			for (Clazz clazz : clazzList) {
				ClazzConditions clazzConditions = clazz.getConditions();
				ClazzExports clazzExports = clazz.getExports();
				webServicesList.addAll(filterClasses(clazzConditions));
			}
		}

		return resultMap;
	}

	@Override
	public void setGraph(List<Object> graph) {
		this.graph = graph;
	}

	private List<Object> filterClasses(ClazzConditions filterClazz) {
		List<Object> resultList = new ArrayList<Object>();
		List<Type> graphClasses = getAllClassesFromGraph();
		resultList.addAll(graphClasses);

		Annotations annotations = filterClazz.getAnnotations();
		Fields fields = filterClazz.getFields();
		Interfaces interfaces = filterClazz.getInterfaces();
		Methods methods = filterClazz.getMethods();
		Modifiers modifiers = filterClazz.getModifiers();
		String name = filterClazz.getName();
		String superclass = filterClazz.getSuperclass();

		for (Type graphClass : graphClasses) {
			String className = graphClass.getName();
			if ((name != null) && (!className.startsWith(name))) {
				resultList.remove(graphClass);
				continue;
			}
			if (modifiers != null) {
				String modifiersValue = modifiers.value();
				int classModifiers = graphClass.getModifiers();
				String classModifiersStr = Modifier.toString(classModifiers);
				if (!classModifiersStr.contains(modifiersValue)) {
					resultList.remove(graphClass);
					continue;
				}
			}
			if (superclass != null) {
				MClass classSuperclass = graphClass.getSupertype();
				if (classSuperclass == null) {
					resultList.remove(graphClass);
					continue;
				} else {
					String superClassName = classSuperclass.getName();
					superClassName = superClassName
							.substring("class ".length());
					if (!superclass.equals(superClassName)) {
						resultList.remove(graphClass);
						continue;
					}
				}
			}
			if (annotations != null) {
				List<MAnnotation> classAnnotations = graphClass
						.getAnnotations();
				if (!satisfyAnnotationCondition(classAnnotations, annotations)) {
					resultList.remove(graphClass);
					continue;
				}
			}
			if (fields != null) {
				List<MField> classFields = graphClass.getFields();
				if (!satisfyFieldCondition(classFields, fields)) {
					resultList.remove(graphClass);
					continue;
				}
			}
			if (interfaces != null) {
				List<MInterface> classInterfaces = graphClass.getInterfaces();
				if (!satisfyInterfaceCondition(classInterfaces, interfaces)) {
					resultList.remove(graphClass);
					continue;
				}
			}
			if (methods != null) {
				List<MMethod> classMethods = graphClass.getMethods();
				if (!satisfyMethodCondition(classMethods, methods)) {
					resultList.remove(graphClass);
					continue;
				}
			}
		}

		return resultList;
	}

	private List<Type> getAllClassesFromGraph() {
		List<Type> classes = new ArrayList<Type>();
		for (Object graphComponent : graph) {
			if (graphComponent instanceof MClass) {
				classes.add((MClass) graphComponent);
			} else {
				if (graphComponent instanceof MEnumeration) {
					classes.add((MEnumeration) graphComponent);
				} else {
					if (graphComponent instanceof MException) {
						classes.add((MException) graphComponent);
					}
				}
			}
		}

		return classes;
	}

	private List<MEnumeration> getAllEnumerationsFromGraph() {
		List<MEnumeration> enumerations = new ArrayList<MEnumeration>();
		for (Object graphComponent : graph) {
			if (graphComponent instanceof MEnumeration) {
				enumerations.add((MEnumeration) graphComponent);
			}
		}

		return enumerations;
	}

	private boolean satisfyAnnotationCondition(List<MAnnotation> srcList,
			Annotations annotations) {
		List<Annotation> annotationsList = annotations.getAnnotation();
		List<String> srcClasses = new ArrayList<String>();
		for (MAnnotation srcAnnotation : srcList) {
			String srcAnnotationName = srcAnnotation.getName();
			srcClasses.add(srcAnnotationName);
		}

		for (Annotation annotation : annotationsList) {
			String name = annotation.getName();
			if (!srcClasses.contains(name))
				return false;
		}

		return true;
	}

	private boolean satisfyInterfaceCondition(List<MInterface> srcList,
			Interfaces interfaces) {
		List<Interface> interfacesList = interfaces.getInterface();
		List<String> srcInterfaces = new ArrayList<String>();
		for (MInterface srcInterface : srcList) {
			String srcInterfaceName = srcInterface.getName();
			srcInterfaces.add(srcInterfaceName);
		}

		for (Interface interface1 : interfacesList) {
			String name = interface1.getName();
			if (!srcInterfaces.contains(name))
				return false;
		}

		return true;
	}

	private boolean satisfyExceptionCondition(List<MException> srcList,
			Exceptions exceptions) {
		List<Exception> exceptionsList = exceptions.getException();
		List<String> srcExceptions = new ArrayList<String>();
		for (MException srcException : srcList) {
			String srcExceptionName = srcException.getName();
			srcExceptionName = srcExceptionName
					.substring("exception ".length());
			srcExceptions.add(srcExceptionName);
		}

		for (Exception exception : exceptionsList) {
			String name = exception.getType();
			if (!srcExceptions.contains(name))
				return false;
		}

		return true;
	}

	private boolean satisfyParameterCondition(Collection<Class> srcList,
			Parameters parameters) {
		List<Parameter> parametersList = parameters.getParameter();
		List<String> srcParameters = new ArrayList<String>();
		for (Class srcClass : srcList) {
			String srcClassName = srcClass.getName();
			srcParameters.add(srcClassName);
		}

		for (Parameter parameter : parametersList) {
			String name = parameter.getType();
			if (!srcParameters.contains(name))
				return false;
		}

		return true;
	}

	private boolean satisfyFieldCondition(List<MField> srcList, Fields fields) {
		List<Field> fieldsList = fields.getField();

		boolean containsSuchField = true;
		for (Field field : fieldsList) {
			if (containsSuchField) {
				containsSuchField = false;
				Annotations annotations = field.getAnnotations();
				Modifiers modifiers = field.getModifiers();
				String type = field.getType();
				String name = field.getName();

				for (MField srcField : srcList) {
					if (annotations != null) {
						List<MAnnotation> fieldAnnotations = srcField
								.getAnnotations();
						if (!satisfyAnnotationCondition(fieldAnnotations,
								annotations))
							continue;
					}
					if (modifiers != null) {
						String modifiersValue = modifiers.value();
						int fieldModifiers = srcField.getModifiers();
						String fieldModifiersStr = Modifier
								.toString(fieldModifiers);
						if (!fieldModifiersStr.contains(modifiersValue)) {
							continue;
						}
					}
					if (type != null) {
						Class fieldType = srcField.getType();
						String fieldTypeStr = fieldType.getName();
						if (!type.equals(fieldTypeStr)) {
							continue;
						}
					}
					if (name != null) {
						String fieldName = srcField.getName();
						if (!name.equals(fieldName)) {
							continue;
						}
					}

					containsSuchField = true;
					break;
				}
			}
		}

		return containsSuchField;
	}

	private boolean satisfyMethodCondition(List<MMethod> srcList,
			Methods methods) {
		List<Method> methodsList = methods.getMethod();

		boolean containsSuchMethod = true;
		for (Method method : methodsList) {
			if (containsSuchMethod) {
				containsSuchMethod = false;
				Annotations annotations = method.getAnnotations();
				String defaultValue = method.getDefaultValue();
				Exceptions exceptions = method.getExceptions();
				Modifiers modifiers = method.getModifiers();
				String name = method.getName();
				Parameters parameters = method.getParameters();
				String returnType = method.getReturnType();

				for (MMethod srcMethod : srcList) {
					if (annotations != null) {
						List<MAnnotation> methodAnnotations = srcMethod
								.getAnnotations();
						if (!satisfyAnnotationCondition(methodAnnotations,
								annotations))
							continue;
					}
					if (defaultValue != null) {
						Object methodDefaultValue = srcMethod.getDefaultValue();
						String methodDefaultValueStr = "";
						if (methodDefaultValue != null) {
							methodDefaultValueStr = methodDefaultValue
									.toString();
						}
						if (!defaultValue.equals(methodDefaultValueStr))
							continue;
					}
					if (exceptions != null) {
						List<MException> methodExceptions = srcMethod
								.getExceptions();
						if (!satisfyExceptionCondition(methodExceptions,
								exceptions))
							continue;
					}
					if (modifiers != null) {
						String modifiersValue = modifiers.value();
						int fieldModifiers = srcMethod.getModifiers();
						String fieldModifiersStr = Modifier
								.toString(fieldModifiers);
						if (!fieldModifiersStr.contains(modifiersValue)) {
							continue;
						}
					}
					if (name != null) {
						String methodName = srcMethod.getName();
						if (!methodName.equals(name))
							continue;
					}
					if (parameters != null) {
						Map<String, Class> methodParameters = srcMethod
								.getParameters();
						if (!satisfyParameterCondition(methodParameters
								.values(), parameters))
							continue;
					}
					if (returnType != null) {
						Class methodReturnType = srcMethod.getReturnType();
						String methodReturnTypeStr = methodReturnType.getName();
						if (!methodReturnTypeStr.equals(returnType))
							continue;
					}

					containsSuchMethod = true;
					break;
				}
			}
		}

		return containsSuchMethod;
	}

	private boolean satisfyFieldConditionForExport(List<MField> srcList,
			Fields fields) {
		List<Field> fieldsList = fields.getField();

		boolean containsSuchField = true;
		for (Field field : fieldsList) {
			containsSuchField = false;
			Annotations annotations = field.getAnnotations();
			Modifiers modifiers = field.getModifiers();
			String type = field.getType();
			String name = field.getName();

			for (MField srcField : srcList) {
				if (annotations != null) {
					List<MAnnotation> fieldAnnotations = srcField
							.getAnnotations();
					if (!satisfyAnnotationCondition(fieldAnnotations,
							annotations))
						continue;
				}
				if (modifiers != null) {
					String modifiersValue = modifiers.value();
					int fieldModifiers = srcField.getModifiers();
					String fieldModifiersStr = Modifier
							.toString(fieldModifiers);
					if (!fieldModifiersStr.contains(modifiersValue)) {
						continue;
					}
				}
				if (type != null) {
					Class fieldType = srcField.getType();
					String fieldTypeStr = fieldType.getName();
					if (!type.equals(fieldTypeStr)) {
						continue;
					}
				}
				if (name != null) {
					String fieldName = srcField.getName();
					if (!name.equals(fieldName)) {
						continue;
					}
				}

				containsSuchField = true;
				return true;
			}
		}

		return containsSuchField;
	}

	private boolean satisfyMethodConditionForExport(List<MMethod> srcList,
			Methods methods) {
		List<Method> methodsList = methods.getMethod();

		boolean containsSuchMethod = true;
		for (Method method : methodsList) {

			containsSuchMethod = false;
			Annotations annotations = method.getAnnotations();
			String defaultValue = method.getDefaultValue();
			Exceptions exceptions = method.getExceptions();
			Modifiers modifiers = method.getModifiers();
			String name = method.getName();
			Parameters parameters = method.getParameters();
			String returnType = method.getReturnType();

			for (MMethod srcMethod : srcList) {
				if (annotations != null) {
					List<MAnnotation> methodAnnotations = srcMethod
							.getAnnotations();
					if (!satisfyAnnotationCondition(methodAnnotations,
							annotations))
						continue;
				}
				if (defaultValue != null) {
					Object methodDefaultValue = srcMethod.getDefaultValue();
					String methodDefaultValueStr = "";
					if (methodDefaultValue != null) {
						methodDefaultValueStr = methodDefaultValue.toString();
					}
					if (!defaultValue.equals(methodDefaultValueStr))
						continue;
				}
				if (exceptions != null) {
					List<MException> methodExceptions = srcMethod
							.getExceptions();
					if (!satisfyExceptionCondition(methodExceptions, exceptions))
						continue;
				}
				if (modifiers != null) {
					String modifiersValue = modifiers.value();
					int fieldModifiers = srcMethod.getModifiers();
					String fieldModifiersStr = Modifier
							.toString(fieldModifiers);
					if (!fieldModifiersStr.contains(modifiersValue)) {
						continue;
					}
				}
				if (name != null) {
					String methodName = srcMethod.getName();
					if (!methodName.equals(name))
						continue;
				}
				if (parameters != null) {
					Map<String, Class> methodParameters = srcMethod
							.getParameters();
					if (!satisfyParameterCondition(methodParameters.values(),
							parameters))
						continue;
				}
				if (returnType != null) {
					Class methodReturnType = srcMethod.getReturnType();
					String methodReturnTypeStr = methodReturnType.getName();
					if (!methodReturnTypeStr.equals(returnType))
						continue;
				}

				containsSuchMethod = true;
				return true;
			}
		}

		return containsSuchMethod;
	}

	private List<Object> modifyEntitiesForExport(List<Object> filteredClasses,
			ClazzExports clazzExports) {

		if (clazzExports == null) {
			return filteredClasses;
		}

		Fields fields = clazzExports.getFields();
		Methods methods = clazzExports.getGeneralMethods();
		Methods queiresMethods = clazzExports.getQueryMethods();

		for (Object filteredClassObject : filteredClasses) {
			Type filteredClass = (Type) filteredClassObject;

			// removing fields
			if (fields != null) {
				List<MField> filteredClassFields = filteredClass.getFields();
				List<MField> forIterating = new ArrayList<MField>();
				forIterating.addAll(filteredClassFields);
				List<MField> listForFiltering = new ArrayList<MField>();
				for (MField field : forIterating) {
					listForFiltering.clear();
					listForFiltering.add(field);
					if (!satisfyFieldConditionForExport(listForFiltering,
							fields)) {
						filteredClassFields.remove(field);
					}
				}
			}

			// removing general methods & creating list of queries methods
			List<MMethod> queriesMethods = new ArrayList<MMethod>();
			List<MMethod> filteredClassMethods = filteredClass.getMethods();
			List<MMethod> forIterating = new ArrayList<MMethod>();
			forIterating.addAll(filteredClassMethods);
			List<MMethod> methodListForFiltering = new ArrayList<MMethod>();
			for (MMethod method : forIterating) {
				methodListForFiltering.clear();
				methodListForFiltering.add(method);
				if (methods != null) {
					if (!satisfyMethodConditionForExport(
							methodListForFiltering, methods)) {
						filteredClassMethods.remove(method);
					}
				}
				if (queiresMethods != null) {
					if (satisfyMethodConditionForExport(methodListForFiltering,
							queiresMethods)) {
						queriesMethods.add(method);
					}
				}
			}
			filteredClass.setQueriesMethods(queriesMethods);
		}

		return filteredClasses;
	}

	private List<Object> modifyServicesForExport(List<Object> filteredClasses,
			ClazzExports clazzExports) {

		Methods methods = null;
		if (clazzExports != null) {
			methods = clazzExports.getGeneralMethods();
		}

		for (Object filteredClassObject : filteredClasses) {
			MClass filteredClass = (MClass) filteredClassObject;

			// removing all fields
			List<MField> fields = filteredClass.getFields();
			fields.clear();

			if (clazzExports != null) {
				// removing general methods
				List<MMethod> filteredClassMethods = filteredClass.getMethods();
				List<MMethod> forIterating = new ArrayList<MMethod>();
				forIterating.addAll(filteredClassMethods);
				List<MMethod> methodListForFiltering = new ArrayList<MMethod>();
				for (MMethod method : forIterating) {
					methodListForFiltering.clear();
					methodListForFiltering.add(method);
					if (methods != null) {
						if (!satisfyMethodConditionForExport(
								methodListForFiltering, methods)) {
							filteredClassMethods.remove(method);
						}
					}
				}
			}
		}

		return filteredClasses;
	}

	private List<Object> modifyValueObjectsForExport(
			List<Object> filteredClasses, ClazzExports clazzExports) {

		Fields fields = null;
		String valueObjectTarget = "";
		if (clazzExports != null) {
			fields = clazzExports.getFields();
			valueObjectTarget = clazzExports.getValueObjectTarget();
		}

		for (Object filteredClassObject : filteredClasses) {
			MClass filteredClass = (MClass) filteredClassObject;
			if (!valueObjectTarget.equals(""))
				filteredClass.setValueObjectTarget(valueObjectTarget);

			// removing fields
			if (fields != null) {
				List<MField> filteredClassFields = filteredClass.getFields();
				List<MField> forIterating = new ArrayList<MField>();
				forIterating.addAll(filteredClassFields);
				List<MField> listForFiltering = new ArrayList<MField>();
				for (MField field : forIterating) {
					listForFiltering.clear();
					listForFiltering.add(field);
					if (!satisfyFieldConditionForExport(listForFiltering,
							fields)) {
						filteredClassFields.remove(field);
					}
				}
			}

			// removing all methods
			List<MMethod> methods = filteredClass.getMethods();
			methods.clear();

		}

		return filteredClasses;
	}
}
